package com.googlecode.afdbaccess.web.common.login;

import java.security.Principal;
import java.security.acl.Group;
import java.util.Map;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.sql.DataSource;

import org.jboss.security.auth.spi.UsernamePasswordLoginModule;

import com.googlecode.afdbaccess.cache.DbCache;
import com.googlecode.afdbaccess.cache.connectionpool.StandardDataSourcePool;
import com.googlecode.afdbaccess.password.PasswordHasher;

public class StrongHashLoginModule extends UsernamePasswordLoginModule {
    private DbCache dbCache;
    
    private PasswordFetcher passwordFetcher;
    private UserInfoFetcher userInfoFetcher;
    
    private Group[] savedGroups;
    
    public StrongHashLoginModule() {
        this(new PasswordFetcher(), new UserInfoFetcher());
    }
    
    protected StrongHashLoginModule(PasswordFetcher passwordFetcher, UserInfoFetcher userInfoFetcher) {
        this.passwordFetcher = passwordFetcher;
        this.userInfoFetcher = userInfoFetcher;
    }
    
    @Override
    public boolean login() throws LoginException {
        if (super.login()) {
            savedGroups = getRoleSetsInternal(getUsername());
            return true;
        }
        return false;
    }

    @Override
    protected Principal getIdentity() {
        if (savedGroups == null) {
            return super.getIdentity();
        } else {
            return savedGroups[1].members().nextElement();
        }
    }

    @Override
    public void initialize(Subject pSubject, CallbackHandler pCallbackHandler, Map<String,?> pSharedState, Map<String,?> pOptions) {
        try {
            super.initialize(pSubject, pCallbackHandler, pSharedState, pOptions);
    
            initializeDbPool(pOptions);
        } catch (Throwable throwable) {
            log.debug("Got an exception. Logging it and rethrowing it, since the higher layers don't seem to log it ever.", throwable);
            
            if(throwable instanceof RuntimeException) {
                throw (RuntimeException)throwable;
            } else {
                throw new RuntimeException("Init failed.", throwable);
            }
        }
    }

    protected void initializeDbPool(Map<String, ?> pOptions) {
        String dsJndiName = (String)pOptions.get("dsJndiName");
        
        DataSource ds = getDataSource(dsJndiName);
        
        StandardDataSourcePool pool = new StandardDataSourcePool(ds);
        dbCache = new DbCache(pool);
    }

    protected DataSource getDataSource(String dsJndiName) {
        try {
            return (DataSource)InitialContext.doLookup(dsJndiName);
        } catch (NamingException exception) {
            throw new RuntimeException("Couldn't do lookup for database at jndi-path: " + dsJndiName, exception);
        }
    }
    
    //
    
    @Override
    protected String getUsersPassword() throws LoginException {
        return passwordFetcher.performWithErrorHandlingAndCleanup(dbCache, "getUsersPassword()", getUsername());
    }
    
    protected Group[] getRoleSetsInternal(String username) throws LoginException {
        return userInfoFetcher.performWithErrorHandlingAndCleanup(dbCache, "getRoleSetsInternal()", username);
    }

    @Override
    protected Group[] getRoleSets() throws LoginException {
        return savedGroups;
    }
    
    @Override
    protected boolean validatePassword(String inputPassword, String hashedPasswordFromUserList) {
        log.debug("validatePassword(). userName: " + super.getUsername());
        return PasswordHasher.checkPassword(inputPassword, hashedPasswordFromUserList);
    }
}
